#include "nvm.h"
#include "utils/crc16.h"
#include "bsp/cpu_driver.h"
#include "utils/logger.h"
#include "os/os.h"

#define NVM_MAGIC 0x12349876

typedef struct {
	u32  crc32;
	u32  len;
	u32  index;
}block_head_t;

typedef struct {
	u32 flash_address[2];
	u32 flash_size;
#ifdef CONFIG_NVM_RAM_CACHE
	u32 cache;
#endif
	u32 curr_index;
	int read_block;
	bool error;
}nvm_block_t;

/* nvm flash layout 
|------------------|
    nvm_head_t
|------------------|
      data
|------------------|
*/
static nvm_block_t _nvm_blocks[CONFIG_NVM_PICES_NUM];
static u32 block_crc32(nvm_block_t *blocks, int b) {
	u32 address = blocks->flash_address[b];
	block_head_t *flash_header = (block_head_t *)address;
	return crc32_get((u8 *)(&flash_header->len), flash_header->len + 8);
}


static int _check_one_block(nvm_block_t *blocks, int b) {
	block_head_t *head = (block_head_t *)blocks->flash_address[b];
	if (head->crc32 == 0xFFFFFFFF || head->len == 0xFFFFFFFF || head->index == 0xFFFFFFFF || head->len > blocks->flash_size) {
		sys_debug("data%d is not inited\n", b);
		return -1; //head error, need erase
	}
	if (head->crc32 != block_crc32(blocks, b)){
		sys_debug("data%d is crc error 0x%x\n", b, head->crc32);
		return -1;
	}
	return head->index;
}

static void nvm_init_(nvm_block_t *blocks) {
	int index0 = _check_one_block(blocks, 0);
	int index1 = _check_one_block(blocks, 1);
	blocks->error = false;
	sys_debug("NVM: get index -> %d, %d\n", index0, index1);
	if (index0 > index1) {
		blocks->read_block = 0;
		blocks->curr_index = index0;
	}else if (index1 > index0) {
		blocks->read_block = 1;
		blocks->curr_index = index1;
	}else if (index0 != -1) {
		blocks->read_block = 0;
		blocks->curr_index = index0;
	}else {
		blocks->read_block = 0;
		blocks->curr_index = 0;
		blocks->error = true;
	}
#ifdef CONFIG_NVM_RAM_CACHE
	if (!blocks->error) {
		memcpy((void *)blocks->cache, (void *)blocks->flash_address[blocks->read_block], blocks->flash_size);
	}
#endif
	sys_debug("select data%d:%d\n", blocks->read_block, blocks->curr_index);
}

void nvm2_init(int nvm_nr, u32 flash_addr[], u32 flash_size) {
	if (nvm_nr >= CONFIG_NVM_PICES_NUM) {
		sys_debug("two many nvm nrs\n");
		return;
	}
	nvm_block_t *blocks = _nvm_blocks + nvm_nr;
	blocks->flash_address[0] = flash_addr[0];
	blocks->flash_address[1] = flash_addr[1];
	sys_debug("block %d, flash addr 0x%x[0x%x]\n", nvm_nr, blocks->flash_address[0], blocks->flash_address[1]);
	blocks->flash_size = flash_size;
#ifdef CONFIG_NVM_RAM_CACHE
	blocks->cache = (u32)os_alloc(flash_size);
#endif
	nvm_init_(blocks);
}

bool nvm_data_avalible(nvm_block_t *blocks) {
	return !blocks->error;
}

s32 nvm_read_(nvm_block_t *blocks, u8 *buffer, u32 len) {
	if (!nvm_data_avalible(blocks)) {
		sys_debug("nvm read block no data\n");
		return NVM_ERR_EMPTY;
	}
#ifdef CONFIG_NVM_RAM_CACHE
	u32 flash_addr = blocks->cache; //read from the cache
#else
	int block_nr = blocks->read_block;
	u32 flash_addr = blocks->flash_address[block_nr];
#endif
	sys_debug("read addr = 0x%x\n", flash_addr);
	block_head_t *header = (block_head_t *)flash_addr;
	if (len != header->len) {
		sys_debug("nvm read Len errror\n");
		return NVM_ERR_LEN;
	}
	
	memcpy(buffer, (void *)(flash_addr + sizeof(block_head_t)), len);
	
	return NVM_ERR_NO;
}

s32 nvm_read(u32 nvm_nr, u8 *buffer, u32 len) {
	if (nvm_nr >= CONFIG_NVM_PICES_NUM) {
		sys_debug("nvm read two many nvm nrs\n");
		return NVM_ERR_EMPTY;
	}
	nvm_block_t *blocks = _nvm_blocks + nvm_nr;
	return nvm_read_(blocks, buffer, len);
}

#ifdef CONFIG_NVM_RAM_CACHE
static u32 get_max_index(void) {
	u32 index = 0;
	for (int i = 0; i < CONFIG_NVM_PICES_NUM; i++) {
		if (index < _nvm_blocks[i].curr_index) {
			index = _nvm_blocks[i].curr_index;
		}
	}
	return index;
}

static void update_all_index(u32 skip, u32 index) {
	for (int i = 0; i < CONFIG_NVM_PICES_NUM; i++) {
		if (skip == i) {
			continue;
		}
		u32 cache = _nvm_blocks[i].cache;
		block_head_t *header = (block_head_t *)cache;
		header->index = index;
		header->crc32 = crc32_get((u8 *)cache + 4, header->len + 8);
	}
}

static void write_all_blocks(u32 block_nr) {
	for (int i = 0; i < CONFIG_NVM_PICES_NUM; i++) {
		nvm_block_t *blocks = _nvm_blocks + i;
		block_head_t *header = (block_head_t *)blocks->cache;
		u32 flash_addr = blocks->flash_address[block_nr];
		flash_write_page(flash_addr, (u8 *)blocks->cache, header->len + sizeof(header), i==0?true:false);
	}
}
#endif

static void _write_one_block(nvm_block_t *blocks, u32 block_nr, u8 *buffer, u32 len) {
	u32 flash_addr = blocks->flash_address[block_nr];
	block_head_t header;
#ifdef CONFIG_NVM_RAM_CACHE
	header.index = get_max_index() + 1;
#else
	header.index = blocks->curr_index + 1;
#endif
	header.len = len;
	u32 crc32 = 0xFFFFFFFF;
	crc32 = crc32_update(crc32, (u8 *)&(header.len), 8);
	crc32 = crc32_update(crc32, buffer, len);
	header.crc32 = crc32_finish(crc32);

#ifdef CONFIG_NVM_RAM_CACHE
	memcpy((void *)blocks->cache, &header, sizeof(header));
	memcpy((void *)(blocks->cache + sizeof(header)), buffer, len);
	update_all_index(block_nr, header.index);
	write_all_blocks(block_nr);
#else
	flash_write_page(flash_addr, (u8 *)&header, sizeof(header), true);
	flash_write_page(flash_addr + sizeof(header), buffer, len, false);
#endif
	sys_debug("write on block addr = 0x%x b=%d, index=%d\n", flash_addr, block_nr, header.index);

	block_head_t *r = (block_head_t *)flash_addr;
	sys_debug("read -> 0x%x,%d,%d, 0x%x\n", r->crc32, r->index, r->len, block_crc32(blocks, block_nr));
}

s32 nvm_write_(nvm_block_t *blocks, u8 *buffer, u32 len) {
	int target_block = 1 - blocks->read_block;
	if (len > blocks->flash_size) {
		sys_debug("nvm write Len error\n");
		return NVM_ERR_LEN;
	}
	_write_one_block(blocks, target_block, buffer, len);
	blocks->read_block = target_block;
	return NVM_ERR_NO;
}

s32 nvm_write(u32 nvm_nr, u8 *buffer, u32 len) {
	if (nvm_nr >= CONFIG_NVM_PICES_NUM) {
		sys_debug("nvm write two many nvm nrs\n");
		return NVM_ERR_EMPTY;
	}
	nvm_block_t *blocks = _nvm_blocks + nvm_nr;
	return nvm_write_(blocks, buffer, len);
}
